package compound.repository

import compound.repository.entries.Compound
import exception.DatabaseError

import org.springframework.beans.factory.InitializingBean
import types.Hit
import util.TypeCastUtil
import edu.ucdavis.genomics.metabolomics.binbase.annotations.Queryable
import util.ConvertInchi

/**
 * simple service to lookup data from the database
 */
class LookupService implements InitializingBean {

    boolean transactional = true

    void afterPropertiesSet() { }

/**
 * can return 0 - n
 */
    @Queryable(name = "smiles")
    Collection<Compound> lookupBySmile(def value, Map params = [:], def wildcards = true) {
        executeQuery("Select a from compound.repository.entries.Compound a, compound.repository.entries.Smiles b where a.id = b.compound.id and b.code = ? order by a.id", value, params, wildcards)

    }

    Long countlookupBySmile(def value, Map params = [:], def wildcards = true) {
        return executeQueryCount("Select count(distinct a.id) from compound.repository.entries.Compound a, compound.repository.entries.Smiles b where a.id = b.compound.id and b.code = ?", value, wildcards)

    }

/**
 * can return 0 - n
 */
    @Queryable(name = "kegg")
    Collection<Compound> lookupByKegg(def value, Map params = [:], def wildcards = true) {
        executeQuery("Select a from compound.repository.entries.Compound a, compound.repository.entries.DBLinks b where a.id = b.compound.id  and b.linkID = ? and b.class = 'compound.repository.entries.KEGG' order by a.id", value, params, wildcards)
    }


    Long countlookupByKegg(def value, Map params = [:], def wildcards = true) {
        return executeQueryCount("Select count(distinct a.id) from compound.repository.entries.Compound a, compound.repository.entries.DBLinks b where a.id = b.compound.id  and b.linkID = ? and b.class = 'compound.repository.entries.KEGG' ", value, wildcards)
    }

/**
 * can return 0 - n
 */
    @Queryable(name = "cid")
    Collection<Compound> lookupByCID(def value, Map params = [:], def wildcards = false) {
        executeQuery("Select a from compound.repository.entries.Compound a, compound.repository.entries.DBLinks b where a.id = b.compound.id  and b.linkID = ? and b.class = 'compound.repository.entries.PubchemCompound'  order by a.id", value.toString(), params, false)
    }

    Long countlookupByCID(def value, Map params = [:], def wildcards = false) {
        def v = TypeCastUtil.getInstance().castIntoInteger(value)
        return executeQueryCount("Select count(distinct a.id) from compound.repository.entries.Compound a, compound.repository.entries.DBLinks b where a.id = b.compound.id  and b.linkID = ? and b.class = 'compound.repository.entries.PubchemCompound' ", v.toString(), false)
    }

/**
 * can return 0-n
 */
    @Queryable(name = "sid")
    Collection<Compound> lookupBySID(def value, Map params = [:], def wildcards = false) {
        return executeQuery("Select a from compound.repository.entries.Compound a, compound.repository.entries.DBLinks b where a.id = b.compound.id  and b.linkID = ? and b.class = 'compound.repository.entries.PubchemSubstance' order by a.id", value.toString(), params, false)
    }

    Long countlookupBySID(def value, Map params = [:], def wildcards = false) {

        def v = TypeCastUtil.getInstance().castIntoInteger(value)
        return executeQueryCount("Select count(distinct a.id) from compound.repository.entries.Compound a, compound.repository.entries.DBLinks b where a.id = b.compound.id  and b.linkID = ? and b.class = 'compound.repository.entries.PubchemSubstance'", v.toString(), false)
    }

/**
 * can return 0-n
 */
    @Queryable(name = "cas")
    Collection<Compound> lookupByCas(def value, Map params = [:], def wildcards = true) {
        return executeQuery("Select a from compound.repository.entries.Compound a, compound.repository.entries.DBLinks b where a.id = b.compound.id and b.linkID = ? and b.class = 'compound.repository.entries.Cas'  order by a.id", value, params, wildcards)
    }

    Long countlookupByCas(def value, Map params = [:], def wildcards = true) {
        return executeQueryCount("Select count(distinct a.id) from compound.repository.entries.Compound a, compound.repository.entries.DBLinks b where a.id = b.compound.id  and b.linkID = ? and b.class = 'compound.repository.entries.Cas'", value, wildcards)

    }

/**
 * can return 0 - 1
 */
    Compound lookupByCompoundId(def value, Map params = [:], def wildcards = true) {
        def v = TypeCastUtil.getInstance().castIntoLong(value)
        return Compound.get(v)
    }

/**
 * can return 0 - n
 */
    @Queryable(name = "inchi")
    def lookupByInchi(def value, Map params = [:], def wildcards = true) {

        def key = ConvertInchi.convertInchiToKey(value)

        return lookupByInchiKey(key, params, wildcards)
    }


    Long countlookupByInchi(def value, Map params = [:], def wildcards = true) {

        def key = ConvertInchi.convertInchiToKey(value)

        return countlookupByInchiKey(key, params, wildcards)
    }

/**
 * can return 0 - n
 */
    @Queryable(name = "formula")
    Collection<Compound> lookupByFormula(def value, Map params = [:], def wildcards = true) {
        return executeQuery("from compound.repository.entries.Compound a where a.formula = ? order by a.id", [value], params, wildcards)
    }

    Long countlookupByFormula(def value, Map params = [:], def wildcards = true) {
        return executeQueryCount("select count(distinct a.id) from compound.repository.entries.Compound a where a.formula = ?", value, wildcards)

    }

/**
 * can return 0 - n
 */
    @Queryable(name = "mass")
    Collection<Compound> lookupByExactMass(def value, Map params = [:], def wildcards = false) {
        return executeQuery("from compound.repository.entries.Compound a where a.exactMolareMass = ? order by a.id", TypeCastUtil.getInstance().castIntoDouble(value), params, false)
    }

    Long countlookupByExactMass(def value, Map params = [:], def wildcards = false) {

        def v = TypeCastUtil.getInstance().castIntoDouble(value)
        return executeQueryCount("select count(distinct a.id) from compound.repository.entries.Compound a where a.exactMolareMass = ?", v, false)

    }

/**
 * can return 0 - n
 */
    @Queryable(name = "inchikey")
    def lookupByInchiKey(def value, Map params = [:], def wildcards = true) {

        Collection<Compound> result = executeQuery("from compound.repository.entries.Compound a where a.inchiHashKey.completeKey = ? order by a.id", value, params, wildcards)

        // Code Added and modified by pradeep on 13-May,2010 for Exception on InchiKey Search : Start
        //if (result.size() == 1) {
        //  return result.asList().get(0)
        //}else
        if (result.size() > 1) {
            throw new DatabaseError("database error, there can not be more than 1 compound for an inchikey!, offending inchi: $value")
        }

        /*else if (result.isEmpty()) {
          throw new EntryNotFoundException("no compound found for: ${value}")
        }
        else {
          throw new DatabaseError("database error, there can not be more than 1 compound for an inchikey!, offending inchi: $value")
        }*/

        return result;
        // Code Added and modified by pradeep on 13-May,2010 for Exception on InchiKey Search : End
    }

    Long countlookupByInchiKey(def value, Map params = [:], def wildcards = true) {
        return executeQueryCount("select count(distinct a.id) from compound.repository.entries.Compound a where a.inchiHashKey.completeKey = ?", value, wildcards)
    }

/**
 * can return 0 - n
 */
    @Queryable(name = "skeleton")
    Collection<Compound> lookupByPartialInchiKey(def value, Map params = [:], def wildcards = true) {
        return executeQuery("from compound.repository.entries.Compound a where a.inchiHashKey.firstBlock = ? order by a.id", value, params, wildcards)
    }

    Long countlookupByPartialInchiKey(def value, Map params = [:], def wildcards = true) {
        return executeQueryCount("select count(distinct a.id) from compound.repository.entries.Compound a where a.inchiHashKey.firstBlock = ?", value, wildcards)
    }

    @Queryable(name = "name")
    Collection<Compound> lookupByName(def value, Map params = [:], def wildcards = true) {
        def val = TypeCastUtil.getInstance().toLowerCase(value);

        return executeQuery("Select distinct a from compound.repository.entries.Compound a, compound.repository.entries.Synonym b where a.id = b.compound.id and LOWER(b.name) like ? order by a.id", val, params, wildcards)
    }

/**
 *
 * converts a lucense query string to a like string, or attemps to. It's a rather simple version of doing this
 * @param
 value
 * @return
 */
    String convertToLikeWildCard(def value) {

        value = value.replaceAll("\\*", "%")
        value = value.replaceAll("\\?", "_")

        return value
    }

/**
 * checks if we have wildcards
 * @param value
 * @return
 */
    boolean hasWildCards(String value) {
        if (value.contains("*")) {
            return true
        }

        if (value.contains("?")) {
            return true
        }

        return false
    }

    Long countlookupByName(def value, Map params = [:], boolean wildcard = true) {
        def val = TypeCastUtil.getInstance().toLowerCase(value);

        return executeQueryCount("Select count(distinct a.id) from compound.repository.entries.Compound a, compound.repository.entries.Synonym b where a.id = b.compound.id and LOWER(b.name) like ?", val, wildcard)

    }


    @Queryable(name = "hmdb")
    Collection<Compound> lookupByHMDB(def value, Map params = [:], boolean wildcard = true) {
        return executeQuery("Select a from compound.repository.entries.Compound a, compound.repository.entries.DBLinks b where a.id = b.compound.id  and b.linkID = ? and b.class = 'compound.repository.entries.HMDB' order by a.id", value, params, wildcard)
    }

    Long countlookupByHMDB(def value, Map params = [:], boolean wildcard = true) {
        return executeQueryCount("Select count(distinct a.id) from compound.repository.entries.Compound a, compound.repository.entries.DBLinks b where a.id = b.compound.id  and b.linkID = ? and b.class = 'compound.repository.entries.HMDB' ", value, wildcard)
    }

    @Queryable(name = "lipidmap")
    Collection<Compound> lookupByLipidMapId(def value, Map params = [:], boolean wildcard = true) {
        return executeQuery("Select a from compound.repository.entries.Compound a, compound.repository.entries.DBLinks b where a.id = b.compound.id  and b.linkID = ? and b.class = 'compound.repository.entries.LipidMAPS'  order by a.id", value, params, wildcard)
    }

    Long countlookupByLipidMapId(def value, Map params = [:], boolean wildcard = true) {
        return executeQueryCount("Select count(distinct a.id) from compound.repository.entries.Compound a, compound.repository.entries.DBLinks b where a.id = b.compound.id  and b.linkID = ? and b.class = 'compound.repository.entries.LipidMAPS'", value, wildcard)
    }


    @Queryable(name = "chebi")
    Collection<Compound> lookupByChebiId(def value, Map params = [:], boolean wildcard = true) {
        if (value.toString().toLowerCase().startsWith("chebi:") == false) {

            value = "CHEBI:${value}";
        }
        value = value.toString().toUpperCase()

        return executeQuery("Select a from compound.repository.entries.Compound a, compound.repository.entries.DBLinks b where a.id = b.compound.id  and b.linkID = ? and b.class = 'compound.repository.entries.ChEBI' order by a.id", value, params, wildcard)
    }

    Long countlookupByChebiId(def value, Map params = [:], boolean wildcard = true) {
        if (value.toString().toLowerCase().startsWith("chebi:") == false) {
            value = "CHEBI:${value}";
        }
        value = value.toString().toUpperCase()

        return executeQueryCount("Select count(distinct a.id) from compound.repository.entries.Compound a, compound.repository.entries.DBLinks b where a.id = b.compound.id  and b.linkID = ? and b.class = 'compound.repository.entries.ChEBI'", value, wildcard)

    }

/**
 * looks up the compound for a single hit
 * can return 0 - n
 */
    Collection<Compound> lookupByHit(Hit hit, Map params = [:]) {
        //contains our results
        Collection<Compound> result = new HashSet<Compound>()

        //find out which type our hit is
        switch (hit.type) {
            case hit.INCHI:
                log.debug("detected inchi...")
                result.add(lookupByInchi(hit.value, params))
                break
            case hit.INCHI_KEY:
                log.debug("detected inchi key...")

                result.add(lookupByInchiKey(hit.value, params))
                break
            case hit.CAS:
                log.debug("detected cas...")

                result = lookupByCas(hit.value, params)
                break
            case hit.KEGG:
                log.debug("detected kegg...")

                result = lookupByKegg(hit.value, params)
                break
            case hit.COMPOUND_ID:
                log.debug("detected compound id...")

                result.add(lookupByCompoundId(hit.compoundId))
                break
            case hit.OSCAR:
                log.debug("detected oscar hit...")

                result = lookupByName(hit.value, params)
                break

            default:
                break
        }

        //print our model
        log.debug("model: ${result}")

        //return the result
        return result
    }

/**
 * execuates the actual query against the database and can be slow for collections, since each entry in the collection
 * is queried
 */
    Collection<Compound> executeQuery(String query, def val, def params, boolean wildcards = true) {

        if (val instanceof Collection) {
            Collection<Compound> result = new HashSet<Compound>()

            val.each {String s ->
                Collection<Compound> search = executeQuery(query, s, params, wildcards)

                result.addAll(search)
            }
            return result
        }
        else {

            if (wildcards) {
                val = convertToLikeWildCard(val)
            }
            return Compound.executeQuery(query, [val], params)

        }
    }

/**
 *
 * calculates the actual count for the query and supports collections and wildcards
 * @param
 query
 * @param val
 * @param wild
 ards
 * @return
 */
    long executeQueryCount(String query, def val, boolean wildcards = true) {
        if (val instanceof Collection) {
            long result = 0

            val.each {def s ->
                result = result + executeQueryCount(query, s, wildcards)

            }
            return result
        }
        else {

            if (wildcards) {
                val = convertToLikeWildCard(val)
            }

            return (Long) (Compound.executeQuery(query, [val]))[0]

        }
    }

}
